import java.util.HashSet;
import java.util.Set;
/**
Contains methods to manipulate 2D main array
*/

public class DataModel {

	/**
	 * number of circles or rows in the data model
	 */
	public static final int NUM_CIRCLES = 4;
	/**
	 * number of sectors or columns in the data model
	 */
	public static final int NUM_SECTORS = 8;

	private final int[][] data;

	/**
	 * Gets cell content as char array.
	 * 
	 * @param x
	 *            row of board
	 * @param y
	 *            column of board
	 * @return returns number at specified location, one char per digit
	 */
	public char[] getCellText(int x, int y) {
		if (data[x][y] == 0) {
			return "".toCharArray();
		}
		return new Integer(data[x][y]).toString().toCharArray();
	}
   
	/**
	 * Gets cell content as int.
	 * 
	 * @param x
	 *            row of board
	 * @param y
	 *            column of board
	 * @return returns int at specified location
	 */
	public int getCellValue(int x, int y) {
		return data[x][y];
	}

	/**
	 * Gets entire board array.
	 * 
	 * @return the board as a 2D int array
	 */
	int[][] getData() {
		return data;
	}

	/**
	 * Construct a new blank board array filled with zeros.
	 */
	public DataModel() {
		data = new int[NUM_CIRCLES][NUM_SECTORS];
	}

	/**
	 * Constructs a new data model using model. Precondition: model must have
	 * same dimensions as data
    @param  model 
                    2D array that is set to data (final 2D array)
	 */
	public DataModel(int[][] model) {
		data = model;
	}

	/**
	 * Fills in a 2 at random available position.
	 */
	public void randomStep() {
		Set<Integer> visitedXs = new HashSet<Integer>();
		while (visitedXs.size() < NUM_CIRCLES) {
			int x = (int) (Math.random() * NUM_CIRCLES);
			if (visitedXs.contains(x)) {
				continue;
			}
			Set<Integer> visitedYs = new HashSet<Integer>();
			while (visitedYs.size() < NUM_SECTORS) {
				int y = (int) (Math.random() * NUM_SECTORS);
				if (visitedYs.contains(y)) {
					continue;
				} else if (data[x][y] != 0) {
					visitedYs.add(y);
					continue;
				}
				data[x][y] = 2;
				// data[x][y] = (int) Math.pow(2, (int) (Math.random() * 12));
				return;
			}
			visitedXs.add(x);
		}
	}

	/**
	 * Rotates array a specified number of units to the left or right.
	 * 
	 * @param a     
                array to be rotated
	 * @param n     
                units to rotate (can be negative for left)
	 */
	public void rotate(int[] a, int n) {
		if (n % a.length == 0) {
			return;
		}
		int startIdx = 0;
		int shifts = 0;
		int currIdx = startIdx;
		int currTemp = a[currIdx];
		while (shifts < a.length) {
			int nextIdx = getNextIndex(a, currIdx, n);
			if (nextIdx == startIdx) {
				a[nextIdx] = currTemp;
				startIdx++;
				currIdx = startIdx;
				currTemp = a[currIdx];
			} else {
				int temp = a[nextIdx];
				a[nextIdx] = currTemp;
				currTemp = temp;
				currIdx = nextIdx;
			}
			shifts++;
		}
	}

	/**
	 * Helper function for rotate.
	 * 
	 * @param a       
                array to rotate
	 * @param idx      
                  index
	 * @param shift   
                    units to shift
	 * @return         
              the next index
	 */
	private int getNextIndex(int[] a, int idx, int shift) {
		// If shift is negative:int shift;

		if (shift < 0) {
			shift = a.length + shift % a.length;
		}
		return (idx + shift) % a.length;
	}

	/**
	 * Shifts the circle by 1 clockwise.
	 * 
	 * @param a       
                array to rotate
	 * @param row    
                row of board to rotate
	 */
	public void rotateCW(int row, int[][] a) {
		int temp = a[row][0];
		int currIdx = 0;
		while (currIdx < NUM_SECTORS - 1) {
			int newTemp = a[row][currIdx + 1];
			a[row][currIdx + 1] = temp;
			temp = newTemp;
			currIdx++;
		}
		a[row][0] = temp;
	}

	/**
	 * Shifts the circle by 1 counterclockwise.
	 * 
	 * @param a      
                array to rotate
	 * @param row   
                row of board to rotate
	 */
	public void rotateCCW(int row, int[][] a) {
		int currIdx = a.length - 1;
		int temp = a[row][currIdx];
		while (currIdx > 0) {
			int newTemp = a[row][currIdx - 1];
			a[row][currIdx] = temp;
			temp = newTemp;
			currIdx--;
		}
		a[row][a.length] = temp;
	}

	/**
	 * Rotates specified row of board.
	 * 
	 * @param row
	 *            row of board to rotate
	 * @param dir
	 *            direction and units to rotate
	 */
	public void rotateData(int row, int dir) {
		rotate(data[row], dir);
	}

	/**
	 * Combines numbers in board towards center of circle.
	 */
	public void combineIn() {
		for (int i = 0; i < NUM_SECTORS; i++) {
			combineColumnIn(i);
		}
	}

	/**
	 * Combines one column towards center of circle.
	 * 
	 * @param col
	 *            column of board to combine
	 */
	public void combineColumnIn(int col) {
		int outerMostUntouchedRow = -1;
		for (int i = 0; i < NUM_CIRCLES; i++) {
			int val = data[i][col];
			int outermostValidRow = -1;
			// Find row into which to merge in
			// Keep searching until the first invalid row.
			for (int j = i - 1; j >= 0 && j > outerMostUntouchedRow; j--) {
				if (!isValidLocation(j, col, data[i][col])) {
					break;
				}
				outermostValidRow = j;
			}
			// If there is a position, add them.
			if (outermostValidRow != -1) {
				if (data[outermostValidRow][col] != 0) {
					outerMostUntouchedRow = outermostValidRow;
				}
				data[outermostValidRow][col] += val;
				data[i][col] = 0;

			}
		}
	}

	/**
	 * Combines numbers in board towards center of circle.
	 */
	public void combineOut() {
		for (int i = 0; i < NUM_SECTORS; i++) {
			combineColumnOut(i);
		}
	}

	/**
	 * Combines one column towards outside of circle.
	 * 
	 * @param col    
                 column of board to combine
	 */
	public void combineColumnOut(int col) {
		int outerMostUntouchedRow = NUM_CIRCLES;
		for (int i = NUM_CIRCLES - 1; i >= 0; i--) {
			int val = data[i][col];
			int outermostValidRow = -1;
			// Find row into which to merge in
			// Keep searching until the first invalid row.
			for (int j = i + 1; j < NUM_CIRCLES && j < outerMostUntouchedRow; j++) {
				if (!isValidLocation(j, col, data[i][col])) {
					break;
				}
				outermostValidRow = j;
			}
			// If there is a position, add them.
			if (outermostValidRow != -1) {
				if (data[outermostValidRow][col] != 0) {
					outerMostUntouchedRow = outermostValidRow;
				}
				data[outermostValidRow][col] += val;
				data[i][col] = 0;
			}
		}
	}

	/**
	 * <<<<<<< .mine detects if specified location is on board and is empty or
	 * matches given value
	 * 
	 * @param row
	 *            row of board
	 * @param col
	 *            column of board
	 * @param value
	 *            value to compare
	 * @return returns whether location is on board and is empty or matches
	 *         given value ======= Detects if specified location is on board and
	 *         is empty or matches given value.
	 * 
	 * @param row
	 *            row of board
	 * @param col
	 *            column of board
	 * @param value
	 *            value to compare
	 * @return returns whether location is on board and is empty or matches
	 *         given value >>>>>>> .r31
	 */
	public boolean isValidLocation(int row, int col, int value) {
		if (row >= 0 && row <= NUM_CIRCLES && row >= 0 && row <= NUM_SECTORS) {
			int v = data[row][col];
			if (v == 0 || v == value) {
				return true;
			}
		}
		return false;

	}

	/**
	 * Detects if game is over.
	 * Written by Kendall Zhu, just testing it with the JUnit tests.
	 * @return  boolean value if game contains number 2048 or not
	 */
	public boolean isGameOver() {
		for (int row = 0; row < NUM_CIRCLES - 1; row++) {
			for (int col = 0; col < NUM_SECTORS; col++) {
				for (int col2 = 0; col2 < NUM_SECTORS; col2++) {
					if (getCellValue(row, col) == 0
							|| getCellValue(row, col) == getCellValue(row + 1,
									col2)) {
						return false;
					}
				}
			}
		}
		for(int col=0; col<NUM_SECTORS; col++)
		{
			if(getCellValue(3,col)==0)
			{
				return false;
			}
		}
		return true;

	}
   /**
   *Detects if the user has won.
   *Written by Erik Shing (edited by Kendall Zhu (stupid semicolon after if() took me half and hour to fix))
   Also Kiara - I don't know why we are doing names now
   @return  boolean value if game contains number 2048 or not
   */
   
  public boolean isGameWon(){ 
     for (int row = 0; row < NUM_CIRCLES; row++) {
        for (int col = 0; col < NUM_SECTORS; col++) {
           if(getCellValue(row, col) == 2048)
           {
               return true;
           }
        }
     }
     return false;
  }           
}
